package com.istock.union.controller;

import java.util.Date;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.support.RequestContext;

import com.istock.base.common.api.model.ApiBaseResponse;
import com.istock.base.common.api.model.RetStatusEnum;
import com.istock.base.enumration.Enums;
import com.istock.union.user.model.Constants;
import com.istock.union.user.model.LoginModel;
import com.istock.union.user.model.LoginforInfo;
import com.istock.union.user.model.SSOUser;
import com.istock.union.user.model.UserOnlineInfo;
import com.istock.union.user.security.InnerTokenService;
import com.istock.union.user.service.AschTaskService;
import com.istock.union.user.service.SSOUserService;
import com.istock.union.user.utils.WebCommonUtils;

import eu.bitwalker.useragentutils.UserAgent;

/**
 * @author senvon
 *
 */
@Controller
@RequestMapping("pre")
public class LoginByPasswdController {

	
	@Value("${sso.login.verifyCount:3}")
	private Integer overVerifyCount;
	
	@Value("${sso.token.storeType:cookie}")
	private String tokenStoreType;
	
	@Value("${sso.token.name:logintoken}")
	private String tokenName;
	
	@Autowired
	private SSOUserService ssoUserService;
	
	@Autowired
	private InnerTokenService tokenService;
	
	@Autowired
	private AschTaskService aschTaskService;
	
	@Value("${sso.url.success}")
	private String loginSuccessUrl;
	
	@Value("${sso.url.fromUrl:false}")
	private boolean fromUrlEnable = false;
	
	private Logger logger = LoggerFactory.getLogger(getClass());
	
	/**用户名密码的登录
	 * 
	 * 登录流程中的关键步骤
	 * 1.判断有效性
	 * 		判断验证码,用户名,密码不能为空
	 * 2.调用loginService,进行数据库验证
	 * 3.数据库验证成功,调用innerTokenService,分配token
	 * 		一般不允许分配失败,如果pc端限制了登录次数,则T掉最老的登录token信息
	 * 4.写入登录标识
	 * 
	 */
	@RequestMapping("login")
	public String login(ModelMap model , LoginModel loginModel , HttpServletRequest request , HttpServletResponse response){
		model.put("loginModel", loginModel);
		
		CheckResult result = doLogin(loginModel, request);
		if(!result.isFlag()) {
			model.put("msg",result.getMessage());
			return "forward:showLogin";
		}
		
		//如果验证通过,生成token
		SSOUser userInfo = result.getSsoUser();
		request.getSession(true).removeAttribute("loginTime");
		return loginSuccess(model , userInfo , request , response);
	}
	
	//登录动作在这个示例程序里面可能是最复杂的业务逻辑了
	//这部分的业务逻辑的写法,适合所有复杂的业务处理
	//写在这边的废话,让有缘人看到
	public CheckResult doLogin(LoginModel loginModel , HttpServletRequest request ) {
		//先进行输入验证
		CheckResult inputResult = loginInputCheck(loginModel, request);
		
		//输入没问题,就会记录登录记录,看看有谁来黑系统
		LoginforInfo loginforInfo = new LoginforInfo();
		UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
		loginforInfo.setBrowser(userAgent.getBrowser().getName());
		loginforInfo.setIpaddr(WebCommonUtils.findRemoteAddr(request));
//		loginforInfo.setLoginLocation(loginLocation);
		loginforInfo.setLoginName(loginModel.getUserName());
		loginforInfo.setLogType(Enums.getInstance().getEnumsValueByCode("LOG_TYPE", "LOG_TYPE_IN"));
		loginforInfo.setOs(userAgent.getOperatingSystem().getName());
		loginforInfo.setTenantCode(loginModel.getTenantCode());
		loginforInfo.setOperTime(new Date());
		if(!inputResult.isFlag()) {
			loginforInfo.setResult(inputResult.getMessage());
			aschTaskService.loginInfoRecord(loginforInfo);
			return inputResult;
		}
		
		//验证用户名密码等服务端验证
		CheckResult serverResult = loginServerCheck(loginModel , request);
		loginforInfo.setResult(serverResult.getMessage());
		//得到结果以后,异步修改登录记录
		aschTaskService.loginInfoRecord(loginforInfo);
		return serverResult;
	}
	
	private CheckResult loginServerCheck(LoginModel loginModel , HttpServletRequest request) {
		//在进行数据库验证之前,对用户的登录次数进行记录,如果登录次数大于一定的数据,就需要展示验证码
		RequestContext requestContext = new RequestContext(request);
		
		//调用用户名密码验证接口
		ApiBaseResponse<SSOUser> checkResponse = ssoUserService.checkPasswd(loginModel.getUserName(), loginModel.getPasswd(), loginModel.getUserName().equals("admin")?"":loginModel.getTenantCode());
		if(checkResponse == null){
			CheckResult result = new CheckResult();
			result.setFlag(false);
			result.setMessage("密码校验服务无法调通");
			return result;
		}
		
		if(checkResponse.getStatus() == RetStatusEnum.FAIL || checkResponse.getResult() == null){
			CheckResult result = new CheckResult();
			result.setFlag(false);
			if(StringUtils.isNotBlank(checkResponse.getMessage())){
				result.setMessage(checkResponse.getMessage());
			}else{
				result.setMessage(requestContext.getMessage("user.password.not.match"));
			}
			return result;
		}
		
		//密码校验通过,向服务器申请token,同时记录登录日志
		tokenApply(checkResponse.getResult() , true , request);
		
		CheckResult result = new CheckResult();
		result.setFlag(true);
		result.setSsoUser(checkResponse.getResult());
		result.setMessage("登录成功");
		return result;
	}
	
	@RequestMapping("vueLogin")
	@ResponseBody
	public ApiBaseResponse<String> vueLogin(LoginModel loginModel , HttpServletRequest request , HttpServletResponse response){
		CheckResult result = doLogin(loginModel, request);
		if(!result.isFlag()) {
			return new ApiBaseResponse<String>(RetStatusEnum.FAIL,Constants.API_RESPONSE_FAIL_CODE,result.getMessage(),"");
		}
		
		//如果验证通过,生成token
		SSOUser userInfo = result.getSsoUser();
		WebCommonUtils.addCookie(response, tokenName, userInfo.getToken(), null,-1 , null);
		return tokenApply(userInfo , true , request);
	}
	
//	@RequestMapping("tokenApply")
	@ResponseBody
	public ApiBaseResponse<String> tokenApply(SSOUser ssoUser , boolean checkMaxCount , HttpServletRequest request){
		//申请token,一定成功
		ApiBaseResponse<String> tokenResult = tokenService.generateToken(ssoUser , checkMaxCount);
		//记录用户在线记录
		UserOnlineInfo userOnlineInfo = new UserOnlineInfo();
		UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("User-Agent"));
		userOnlineInfo.setBrowser(userAgent.getBrowser().getName());
		userOnlineInfo.setIpaddr(WebCommonUtils.findRemoteAddr(request));
		userOnlineInfo.setLoginName(ssoUser.getLoginName());
		userOnlineInfo.setOs(userAgent.getOperatingSystem().getName());
		userOnlineInfo.setSessionId(tokenResult.getResult());
		userOnlineInfo.setTenantCode(ssoUser.getTenantNo());
		if(ssoUser.getCurrentDept() != null) {
			userOnlineInfo.setDeptCode(ssoUser.getCurrentDept().getDeptCode());
		}
		aschTaskService.userOnlineRecord(userOnlineInfo);
		
		return tokenResult;
	}
	
	/**处理输入性的检查
	 * @param loginModel
	 * @param request
	 * @return
	 */
	private CheckResult loginInputCheck(LoginModel loginModel , HttpServletRequest request) {
		RequestContext requestContext = new RequestContext(request);
		if(loginModel == null){
			CheckResult result = new CheckResult();
			result.setFlag(false);
			result.setMessage(requestContext.getMessage("not.null"));
			return result;
		}
		if(StringUtils.isBlank(loginModel.getUserName())){
			CheckResult result = new CheckResult();
			result.setFlag(false);
			result.setMessage(requestContext.getMessage("not.null" , new String[] {requestContext.getMessage("user.userName")}));
			return result;
		}
		if(StringUtils.isBlank(loginModel.getPasswd())){
			CheckResult result = new CheckResult();
			result.setFlag(false);
			result.setMessage("密码没有输入");
			return result;
		}
		
		//已经启用验证码
		//TODO 启用验证码的是,这边一定要开启nginx的会话保持,也会影响一个浏览器,多个标签页登录不同的用户,需要在前端配置一个过滤器,无论怎么样,都需要在请求中设置一个唯一值
		//保持会话过程中的,对于验证码的不断生成,造成侵占缓存资源
//		HttpServletRequest request = (HttpServletRequest)XssSqlFilter.findRequest();
		HttpSession session = request.getSession(true);
		Integer loginTime = NumberUtils.toInt(session.getAttribute("loginTime")+"", 0);
		
		if(overVerifyCount >= 0 && loginTime >= overVerifyCount){
			String expendVerifyCode = session.getAttribute("verifyCode")+"";
			if(!expendVerifyCode.equalsIgnoreCase(loginModel.getVerifyCode())){
				logger.info("期望的验证码是{},输入的是{}" , expendVerifyCode , loginModel.getVerifyCode());
				CheckResult result = new CheckResult();
				result.setFlag(false);
				result.setMessage("验证码不正确,请重新输入");
				return result;
			}
		}
		
		loginTime++;
		logger.debug("loginTime:{} , overVerifyCount:{}" , loginTime , overVerifyCount);
		session.setAttribute("loginTime", loginTime);
		
		CheckResult result = new CheckResult();
		result.setFlag(true);
		result.setMessage("登录成功");
		return result;
	}
	
	class CheckResult{
		private String message;
		private boolean flag;
		private SSOUser ssoUser;
		public String getMessage() {
			return message;
		}
		public void setMessage(String message) {
			this.message = message;
		}
		public boolean isFlag() {
			return flag;
		}
		public void setFlag(boolean flag) {
			this.flag = flag;
		}
		public SSOUser getSsoUser() {
			return ssoUser;
		}
		public void setSsoUser(SSOUser ssoUser) {
			this.ssoUser = ssoUser;
		}
	}
	
	/**登录成功后的设置和跳转
	 * 可以用于无密登录
	 * 也可以用于和其他SSO集成以后的页面跳转.
	 * 
	 * userInfo一定要经过登录认证,获得token,否则无法写入登录信息
	 * 登录可以使用tokenApply接口
	 * 这2个接口混合使用,可以让用户跳过密码验证过程,直接登录.
	 * @param model
	 * @param userInfo
	 * @param request
	 * @param response
	 * @return
	 */
//	@RequestMapping("loginSuccess")
	public String loginSuccess(ModelMap model , SSOUser userInfo , HttpServletRequest request , HttpServletResponse response) {
		//登录成功
		//将token写入浏览器
		if(tokenStoreType.equalsIgnoreCase("cookie")){
			WebCommonUtils.addCookie(response, tokenName, userInfo.getToken(), null,-1 , null);
		}else{
			model.put("token", userInfo.getToken());
			model.put("tokenName", tokenName);
			model.put("set", true);
		}
		//跳转到登录页面
		String fromUrl = request.getParameter("fromUrl");
		if(fromUrlEnable && StringUtils.isNotBlank(fromUrl)) {
			model.put("targetUrl", WebCommonUtils.generateTargetUrl(request.getContextPath(), fromUrl));
		}else {
			model.put("targetUrl", WebCommonUtils.generateTargetUrl(request.getContextPath(), loginSuccessUrl));
		}
		
		model.put("type", "post");
		return "login/redirect";
	}
}
